
本篇文章除了要來介紹 undefined、null 在 strictNullChecks 的不同設置下的差異
還會介紹到「兩者的可指派性」和 「TypeScript 的特殊語法 - 非空斷言操作符(!)」
🔗 本文有提到的其他型別,文章連結整理在這
null 和 undefined 都是 JavaScript 的原始型別 (primitive type)
在 TypeScript 中,undefined、null 的行為會由 tsconfig.json 中的 strictNullChecks 是開啟還是關閉而有不同
// tsconfig.json 
"compilerOptions": {
   "strictNullChecks": false,
   // ... 略 ...
}
let str: string = null; // ✅ Pass
let num: number = undefined; // ✅ Pass
a 和 變數 b 接會被推斷為 any
let a = null;
let b = undefined;

strict 為 true 時,strictNullChecks 會自動被啟用// tsconfig.json 
"compilerOptions": {
   "strictNullChecks": true,
   // ... 略 ...
}
a 和 變數 b 會被分別推斷為 undefined 和 null
let a = undefined; // : undefined
let b = null;      // : null
let str: string = null; // ❌ Type 'null' is not assignable to type 'string'.
let num: number = undefined; // ❌ Type 'undefined' is not assignable to type 'number'.
我們已經知道當開啟 strictNullChecks 時,null 和 undefined 僅能賦值給自己,但其實也可以利用「聯合型別」(如 string | null 或 number | undefined)來巧妙避開檢查機制
想了解「聯合型別」,歡迎看之前寫的文章
🔗 聯合型別和交集型別
function doSomething(x: string | null) {
  if (x === null) {
    // ... 略 ...
  } else {
    console.log(x.toUpperCase()); // ✅ Pass
  }
}
如果環境狀況允許,官方建議把 strictNullChecks 打開,以防非預期錯誤發生
非空斷言操作符(!)是 TypeScript 的特殊語法,在任何表達式後加上 !,表示會忽略對 null 或 undefined 的型別檢查,告訴 TypeScript :「我知道值不會是 null 或 undefined」
以下方例子來說,當操作變數的時候,就算有給定初始值,編譯時仍然會報錯,因為 a 還是有機會會出現 null
function test(a: string | null = 'apple') {
  console.log(a.toUpperCase()); // ❌ 'a' is possibly 'null'.
}
如果要避免以上情況發生,除了可以用 typeof, instantOf 等來 narrowing type,還有更簡潔的方式就是加上 !:
function test(a: string | null = 'apple') {
  console.log(a!.toUpperCase());   // ✅ Pass
} 
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());  // ✅ Pass
}
💡 ! 不能濫用
如果在 ! 的使用上過於大意,可能在運行時,會出現 TypeError 錯誤
建議使用「條件檢查」或 「Optional Chaining ?.」相比於使用 ! 更安全
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());  // ✅ Pass
}
// 傳入 null 或 undefined,會在運行時報錯 ❌ TypeError: Cannot read properties of null (reading 'toFixed')
liveDangerously(null);
liveDangerously(undefined);

圖片來源
又是這張從 Day16 就開始出現的圖,不知道有沒有人跟我一樣始終覺得這張圖看起來不怎抹直觀
橫軸是「目標型別」;縱軸是「來源型別」
藍色勾勾(✓):來源型別可以被指派給目標型別
綠色勾勾(✓):tsconfig.json 中的 strictNullChecks 關閉時才能被指派
這裡就先專注看 undefined 和 nul 就好,這張圖想告訴我們
官方文件上還補充道
當 strictNullChecks 關閉時,null 和 undefined 的行為與 never 類似,它們可以指派給大多數型別,但大多數型別無法指派給它們,畢竟 never 是屬於 the bottom type(底層型別),越底層的型別,能夠接受的範圍越窄
當 strictNullChecks 開啟時,null 和 undefined 的行為與 void 類似,它們無法被指派給其他型別,也無法將其他型別指派給它們,除了 any、unknown 和 void 外(undefined 始終可以賦值給 void)
以下範例是 strictNullChecks 開啟時會報的錯
let strVal = 'hi';
let nullVal = null;
let undefinedVal = undefined;
strVal = nullVal;       // ❌ Type 'null' is not assignable to type 'string'.
nullVal = strVal;       // ❌ Type 'string' is not assignable to type 'null'.
undefinedVal = strVal; // ❌ Type 'strVal' is not assignable to type 'undefined'.
undefinedVal = nullVal; // ❌ Type 'null' is not assignable to type 'undefined'.
以上其實也不用硬背,單純順手把表格的內容整理一下,踩坑踩多了自然慢慢就會記起來了
strictNullChecks 都設為開啟,更為安全非空斷言操作符 (!)是 TypeScript 的特殊語法,在告訴編譯器說:「可以忽略 null 或 undefined 的型別檢查」,要謹慎使用,它可能造成運行時出現 TypeError
每天的內容有推到 github 上喔